#include "ModelMS3D.h"
#include "../Logger.h"
#include <stdio.h>
#include <stdlib.h>
#include "../Texture.h"
#include "../Shapes3D/Direction3D.h"
#include "../Shapes3D/Point3D.h"
#include "../GameLib.h"
#include <assert.h>

/////////////////////////////
//     MS3D STRUCTURES     //
/////////////////////////////

// byte-align structures
#ifdef _MSC_VER
#	pragma pack( push, packing )
#	pragma pack( 1 )
#	define PACK_STRUCT
#elif defined( __GNUC__ )
#	define PACK_STRUCT	__attribute__((packed))
#else
#	error you must byte-align these structures with the appropriate compiler directives
#endif

typedef unsigned char byte;
typedef unsigned short word;

// File header
struct MS3DHeader
{
	char ID[10];
	int version;
} PACK_STRUCT;

// Vertex information
struct MS3DVertex
{
	byte flags;
	float vertex[3];
	char boneID;
	byte refCount;
} PACK_STRUCT;

// Triangle information
struct MS3DTriangle
{
	word flags;
	word vertexIndices[3];
	float vertexNormals[3][3];
	float s[3], t[3];
	unsigned char smoothingGroup;
	unsigned char groupIndex;
} PACK_STRUCT;

// Material information
struct MS3DMaterial
{
    char name[32];
    float ambient[4];
    float diffuse[4];
    float specular[4];
    float emissive[4];
    float shininess;	// 0.0f - 128.0f
    float transparency;	// 0.0f - 1.0f
    byte mode;	// 0, 1, 2 is unused now
    char texture[128];
    char alphamap[128];
} PACK_STRUCT;

//	Joint information
struct MS3DJoint
{
	unsigned char flags;
	char name[32];
	char parentName[32];
	float rotation[3];
	float translation[3];
	word numRotationKeyframes;
	word numTranslationKeyframes;
} PACK_STRUCT;

// Keyframe data
struct MS3DKeyframe
{
	float m_time;
	float m_parameter[3];
} PACK_STRUCT;

// Default alignment
#ifdef _MSC_VER
#	pragma pack( pop, packing )
#endif

#undef PACK_STRUCT

bool _ModelMS3D::load(const char *filename){
	FILE *f;
	long fileSize;
	char *data;
	int i,j,nVertices,nTriangles,nGroups;
	int *triangleIndices;
	Mesh tMesh;
	Material tMaterial;
	Triangle tTriangle;
	Vertex tVertex;
	Str tempStr;
	
	f = fopen(filename, "rb");
	if (!f)
	{
		errLog.print(Str()<<"Could not open the file. \""<<filename<<"\"");
		return false;
	}
	fseek(f,0,SEEK_END);
	fileSize = ftell(f);
	fseek(f,0,SEEK_SET);
	
	data = new char[fileSize];
	const char *ldata = data;
	fread(data,1,fileSize,f);
	fclose(f);
	
	mesh.empty();
	material.empty();
	triangle.empty();
	vertex.empty();
	
	MS3DHeader *pHeader = ( MS3DHeader* )ldata;
	ldata += sizeof(MS3DHeader);
	
	if (strncmp(pHeader->ID, "MS3D000000", 10) != 0)
	{
		errLog.print("Not a valid Milkshape3D model file.");
		return false;
	}
	
	if (pHeader->version < 3 || pHeader->version > 4)
	{
		errLog.print("Unhandled file version. Only Milkshape3D Version 1.3 and 1.4 is supported.");
		return false;
	}
	
	nVertices = *(word*)ldata;
	//vertex.reserve(nVertices);
	ldata += sizeof(word);
	
	for (i = 0; i < nVertices; i++ )
	{
		MS3DVertex *pVertex = (MS3DVertex*)ldata;
		tVertex.boneID = pVertex->boneID;
		memcpy(tVertex.location, pVertex->vertex, sizeof(float)*3);
		vertex.push_back(tVertex);
		ldata += sizeof(MS3DVertex);
	}
	
	nTriangles = *(word*)ldata;
	//triangle.reserve(nTriangles);
	ldata += sizeof(word);
	
	for (i = 0; i < nTriangles; i++)
	{
		MS3DTriangle *pTriangle = (MS3DTriangle*)ldata;
		int vertexIndices[3] = {pTriangle->vertexIndices[0], pTriangle->vertexIndices[1], pTriangle->vertexIndices[2]};
		float t[3] = {1.0f-pTriangle->t[0], 1.0f-pTriangle->t[1], 1.0f-pTriangle->t[2]};
		memcpy(tTriangle.vertexNormals, pTriangle->vertexNormals, sizeof(float)*3*3 );
		memcpy(tTriangle.s, pTriangle->s, sizeof(float)*3 );
		memcpy(tTriangle.t, t, sizeof(float)*3);
		memcpy(tTriangle.vertexIndices, vertexIndices, sizeof(int)*3);
		
		triangle.push_back(tTriangle);
		
		ldata += sizeof(MS3DTriangle);
	}
	
	nGroups = *(word*)ldata;
	//mesh.reserve(nGroups);
	ldata += sizeof(word);
	for (i = 0; i < nGroups; i++)
	{
		ldata += sizeof(unsigned char);	// flags
		ldata += 32;				// name
		
		word nTriangles = *(word*)ldata;
		ldata += sizeof(word);
		triangleIndices = new int[nTriangles];
		for (j = 0; j < nTriangles; j++)
		{
			triangleIndices[j] = *(word*)ldata;
			ldata += sizeof(word);
		}
		
		char materialIndex = *(char*)ldata;
		ldata += sizeof(char);
		
		tMesh.materialIndex = materialIndex;
		tMesh.numTriangles = nTriangles;
		tMesh.triangleIndices = triangleIndices;
		mesh.push_back(tMesh);
	}
	
	int nMaterials = *(word*)ldata;
	//material.reserve(nGroups);
	ldata += sizeof(word);
	for ( i = 0; i < nMaterials; i++ )
	{
		MS3DMaterial *pMaterial = ( MS3DMaterial* )ldata;
		memcpy(tMaterial.ambient, pMaterial->ambient, sizeof(float)*4);
		memcpy(tMaterial.diffuse, pMaterial->diffuse, sizeof(float)*4);
		memcpy(tMaterial.specular, pMaterial->specular, sizeof(float)*4);
		memcpy(tMaterial.emissive, pMaterial->emissive, sizeof(float)*4);
		tMaterial.shininess = pMaterial->shininess;
		tempStr = filename;
		tempStr.keep(0,tempStr.rfind("/"));
		tempStr << "/" << pMaterial->texture;
		tMaterial.tex = textureDB.make(!tempStr);
		material.push_back(tMaterial);
		ldata += sizeof(MS3DMaterial);
	}
	
	delete []data;
	
	return true;
}
void _ModelMS3D::draw(int &index, double scale){
	bool texEnabled;
	int i,j,k,triangleIndex,materialIndex;
	
	texEnabled = glIsEnabled(GL_TEXTURE_2D);
	
	// Draw by group
	for (i = 0; i < mesh.size(); i++)
	{
		materialIndex = mesh[i].materialIndex;
		if ( materialIndex >= 0 )
		{
			glMaterialfv(GL_FRONT, GL_AMBIENT, material[materialIndex].ambient);
			glMaterialfv(GL_FRONT, GL_DIFFUSE, material[materialIndex].diffuse);
			glMaterialfv(GL_FRONT, GL_SPECULAR, material[materialIndex].specular);
			glMaterialfv(GL_FRONT, GL_EMISSION, material[materialIndex].emissive);
			glMaterialf(GL_FRONT, GL_SHININESS, material[materialIndex].shininess);
			
			if (material[materialIndex].tex != 0)
			{
				//glBindTexture( GL_TEXTURE_2D, material[materialIndex].tex );
				glEnable(GL_TEXTURE_2D);
				material[materialIndex].tex->use();
			}
			else
			{
				glDisable(GL_TEXTURE_2D);
			}
		}
		else
		{
			// Material properties?
			glDisable(GL_TEXTURE_2D);
		}
		
		glBegin(GL_TRIANGLES);
		{
			for (j = 0; j < mesh[i].numTriangles; j++)
			{
				triangleIndex = mesh[i].triangleIndices[j];
				const Triangle* pTri = &triangle[triangleIndex];
				
				for (k = 0; k < 3; k++)
				{
					index = pTri->vertexIndices[k];
					glNormal3fv(pTri->vertexNormals[k]);
					glTexCoord2f(pTri->s[k], pTri->t[k]);
					glVertex3fv(vertex[index].location);
				}
			}
		}
		glEnd();
	}
	
	if (texEnabled)
	{
		glEnable(GL_TEXTURE_2D);
	}
	else
	{
		glDisable(GL_TEXTURE_2D);
	}
	
	//GameLib::setColorA(cc);
}
